package com.xuecheng.media.service.impl;

import com.alibaba.nacos.common.utils.IoUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.j256.simplemagic.ContentInfo;
import com.j256.simplemagic.ContentInfoUtil;
import com.xuecheng.base.model.RestResponse;
import com.xuecheng.base.exception.XueChengPlusException;
import com.xuecheng.base.model.PageParams;
import com.xuecheng.base.model.PageResult;
import com.xuecheng.media.mapper.MediaFilesMapper;
import com.xuecheng.media.mapper.MediaProcessMapper;
import com.xuecheng.media.model.po.MediaProcess;
import com.xuecheng.media.model.query.MediaQueryParams;
import com.xuecheng.media.model.dto.UploadFileParamsDto;
import com.xuecheng.media.model.dto.UploadFileResultDto;
import com.xuecheng.media.model.po.MediaFiles;
import com.xuecheng.media.service.MediaFileService;
import io.minio.GetObjectArgs;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.UploadObjectArgs;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.*;


import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;


/**
 * @author Mr.M
 * @version 1.0
 * @description TODO
 * @date 2022/9/10 8:58
 */
@Slf4j
@Service
public class MediaFileServiceImpl implements MediaFileService {

    @Autowired
    MediaFilesMapper mediaFilesMapper;
    @Autowired
    MediaProcessMapper mediaProcessMapper;
    @Autowired
    MinioClient minioClient;
    @Autowired
    MediaFileService currentProxy;

    @Value("${minio.bucket.files}")
    String bucket_Files;
    @Value("${minio.bucket.videofiles}")
    String bucket_Video;

    @Override
    public PageResult<MediaFiles> queryMediaFiels(Long companyId, PageParams pageParams, MediaQueryParams queryParams) {

        //构建查询条件对象
        LambdaQueryWrapper<MediaFiles> queryWrapper = new LambdaQueryWrapper<>();

        queryWrapper.eq(StringUtils.isNotEmpty(queryParams.getFileType()),MediaFiles::getFileType,queryParams.getFileType());
        queryWrapper.eq(StringUtils.isNotEmpty(queryParams.getAuditStatus()),MediaFiles::getAuditStatus,queryParams.getAuditStatus());
        queryWrapper.like(StringUtils.isNotEmpty(queryParams.getFilename()),MediaFiles::getFilename,queryParams.getFilename());

        //分页对象
        Page<MediaFiles> page = new Page<>(pageParams.getPageNo(), pageParams.getPageSize());
        // 查询数据内容获得结果
        Page<MediaFiles> pageResult = mediaFilesMapper.selectPage(page, queryWrapper);
        // 获取数据列表
        List<MediaFiles> list = pageResult.getRecords();
        // 获取数据总数
        long total = pageResult.getTotal();
        // 构建结果集
        PageResult<MediaFiles> mediaListResult = new PageResult<>(list, total, pageParams.getPageNo(), pageParams.getPageSize());
        return mediaListResult;

    }

    @Override
    public UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, byte[] bytes, String folder, String objectName) {
        //生成文件id，文件的md5值
        String fileId = DigestUtils.md5Hex(bytes);
        //文件名称
        String filename = uploadFileParamsDto.getFilename();
        //构造objectname
        if (StringUtils.isEmpty(objectName)) {
            objectName = fileId + filename.substring(filename.lastIndexOf("."));
        }
        if (StringUtils.isEmpty(folder)) {
            //通过日期构造文件存储路径
            folder = getFileFolder(new Date(), true, true, true);
        }
        //对象名称
        objectName = folder + objectName;
        MediaFiles mediaFiles = null;
        try {
            addMediaFilesToMinIO(bytes, bucket_Files, objectName);
            mediaFiles = currentProxy.addMediaFilesToDb(companyId, fileId, uploadFileParamsDto, objectName, bucket_Files);
            UploadFileResultDto uploadFileResultDto = new UploadFileResultDto();
            BeanUtils.copyProperties(mediaFiles, uploadFileResultDto);
            return uploadFileResultDto;
        } catch (Exception e) {
            e.printStackTrace();
            XueChengPlusException.cast("文件上传出错");
        }
        return null;
    }


    /**
     * @param companyId           机构id
     * @param fileId              文件md5值
     * @param uploadFileParamsDto 上传文件的信息
     * @param bucket              桶
     * @param objectName          对象名称
     * @return com.xuecheng.media.model.po.MediaFiles
     * @description 将文件信息添加到文件表
     */
    @Transactional
    @NotNull
    public MediaFiles addMediaFilesToDb(Long companyId, String fileId, UploadFileParamsDto uploadFileParamsDto, String objectName, String bucket) {
        //从数据库查询文件
        MediaFiles mediaFiles = mediaFilesMapper.selectById(fileId);
        if (mediaFiles == null) {
            mediaFiles = new MediaFiles();
            //拷贝基本信息
            BeanUtils.copyProperties(uploadFileParamsDto, mediaFiles);
            mediaFiles.setId(fileId);
            mediaFiles.setFileId(fileId);
            mediaFiles.setCompanyId(companyId);
            mediaFiles.setFilePath(objectName);

            String extension = null;
            String filename = mediaFiles.getFilename();
            if (StringUtils.isNotEmpty(filename) && filename.indexOf(".") >= 0) {
                extension = filename.substring(filename.lastIndexOf("."));
            }
            String contentType = getMediaTypeByExtension(extension);
            if (contentType.indexOf("image") >= 0 || contentType.indexOf("mp4") >= 0) {
                mediaFiles.setUrl("/" + bucket + "/" + objectName);
            }

            mediaFiles.setBucket(bucket);
            mediaFiles.setCreateDate(LocalDateTime.now());
            mediaFiles.setStatus("1");
            mediaFiles.setAuditStatus("002003");
            int insert = mediaFilesMapper.insert(mediaFiles);
            if (insert < 0) {
                XueChengPlusException.cast("保存文件信息失败");
            }

            //对avi视频添加到待处理任务表
            if ("video/x-msvideo".equals(contentType)) {
                MediaProcess mediaProcess = new MediaProcess();
                BeanUtils.copyProperties(mediaFiles, mediaProcess);
                mediaProcess.setStatus("1");
                mediaProcessMapper.insert(mediaProcess);
            }

        }
        return mediaFiles;
    }

    @Override
    public RestResponse<Boolean> checkFile(String fileMd5) {
        MediaFiles mediaFiles = mediaFilesMapper.selectById(fileMd5);
        if (mediaFiles != null) {
            //桶
            String bucket = mediaFiles.getBucket();
            //存储目录
            String filePath = mediaFiles.getFilePath();
            //文件流
            InputStream stream = null;
            try {
                stream = minioClient.getObject(GetObjectArgs.builder()
                        .bucket(bucket)
                        .object(filePath)
                        .build());
                if (stream != null) {
                    return RestResponse.success(true);
                }
            } catch (Exception e) {
                e.printStackTrace();
                XueChengPlusException.cast("文件查询出错");
            }

        }
        return RestResponse.success(false);
    }

    @Override
    public RestResponse<Boolean> checkChunk(String fileMd5, int chunkIndex) {

        //得到分块文件目录
        String chunkFileFolderPath = this.getChunkFileFolderPath(fileMd5);
        //得到分块文件的路径
        String filePath = chunkFileFolderPath + chunkIndex;
        //文件流
        InputStream stream = null;
        try {
            stream = minioClient.getObject(GetObjectArgs.builder()
                    .bucket(bucket_Video)
                    .object(filePath)
                    .build());
            if (stream != null) {
                return RestResponse.success(true);
            }
        } catch (Exception e) {
            return RestResponse.success(false);
        }
        return RestResponse.success(false);
    }

    @Override
    public RestResponse uploadChunk(String fileMd5, int chunk, byte[] bytes) {

        String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
        String filePath = chunkFileFolderPath + chunk;
        try {
            addMediaFilesToMinIO(bytes, bucket_Video, filePath);
        } catch (Exception e) {
            e.printStackTrace();
            XueChengPlusException.cast("上传分块文件出错");
        }
        return RestResponse.success();
    }

    @Override
    public RestResponse mergechunks(Long companyId, String fileMd5, int chunkTotal, UploadFileParamsDto uploadFileParamsDto) {
        File[] chunkFiles = checkChunkStatus(fileMd5, chunkTotal);
        String filename = uploadFileParamsDto.getFilename();
        String extension = filename.substring(filename.lastIndexOf("."));
        File tempFile = null;
        try {
            try {
                tempFile = File.createTempFile("merge", extension);
            } catch (IOException e) {
                XueChengPlusException.cast("创建临时合成文件出错");
            }

            try (RandomAccessFile raf_write = new RandomAccessFile(tempFile, "rw")) {
                byte[] b = new byte[1024];
                for (File chunkFile : chunkFiles) {
                    try (RandomAccessFile raf_read = new RandomAccessFile(chunkFile, "rw");) {
                        int len = -1;
                        while ((len = raf_read.read(b)) != -1) {
                            raf_write.write(b, 0, len);
                        }
                    }

                }
            } catch (Exception e) {
                XueChengPlusException.cast("合并文件过程出错");
            } finally {
                if (chunkFiles != null) {
                    for (File chunkFile : chunkFiles) {
                        if (chunkFile.exists())
                            chunkFile.delete();
                    }
                }
            }
            FileInputStream inputStream = null;
            try {
                inputStream = new FileInputStream(tempFile);
                String md5Hex = DigestUtils.md5Hex(inputStream);
                if (!fileMd5.equals(md5Hex)) {
                    log.debug("合并文件校验不通过，文件路径：{}，原始文件md5:{}", tempFile.getAbsolutePath(), fileMd5);
                    XueChengPlusException.cast("合并文件校验不通过");
                }
            } catch (Exception e) {
                XueChengPlusException.cast("合并文件校验出错");
            } finally {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            String objectName = getObjectName(fileMd5, extension);
            addMediaFilesToMinIO(tempFile.getAbsolutePath(), bucket_Video, objectName);
            uploadFileParamsDto.setFileSize(tempFile.length());//合并文件的大小
            addMediaFilesToDb(companyId, fileMd5, uploadFileParamsDto, objectName, bucket_Video);
            return RestResponse.success(true);
        } finally {
            if (chunkFiles != null) {
                for (File chunkFile : chunkFiles) {
                    if (chunkFile.exists()) {
                        chunkFile.delete();
                    }
                }
            }
            if (tempFile.exists()) {
                tempFile.delete();
            }
        }
    }

    @Override
    public MediaFiles getFileById(String id) {
        MediaFiles mediaFiles = mediaFilesMapper.selectById(id);
        if (mediaFiles == null) {
            XueChengPlusException.cast("文件不存在");
        }
        String url = mediaFiles.getUrl();
        if (StringUtils.isEmpty(url)) {
            XueChengPlusException.cast("文件还没有处理，请稍后预览");
        }
        return mediaFiles;
    }

    private File[] checkChunkStatus(String fileMd5, int chunkTotal) {
        String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
        File[] chunkFiles = new File[chunkTotal];
        for (int i = 0; i < chunkTotal; i++) {
            File chunkFile = null;
            try {
                chunkFile = File.createTempFile("chunk", null);
            } catch (IOException e) {
                e.printStackTrace();
                XueChengPlusException.cast("创建分块临时文件出错");
            }
            chunkFile = downloadFileFromMinIO(bucket_Video, chunkFileFolderPath + i, chunkFile);
            chunkFiles[i] = chunkFile;
        }
        return chunkFiles;
    }

    @Override
    public File downloadFileFromMinIO(String bucket, String objectName, File file) {
        GetObjectArgs getObjectArgs = GetObjectArgs.builder()
                .bucket(bucket)
                .object(objectName)
                .build();
        try (InputStream inputStream = minioClient.getObject(getObjectArgs);
             FileOutputStream fileOutputStream = new FileOutputStream(file)) {
            IoUtils.copy(inputStream, fileOutputStream);
            return file;
        } catch (Exception e) {
            e.printStackTrace();
            XueChengPlusException.cast("查询分块文件出错");
        }
        return null;
    }


    /**
     * @param filePath   文件路径
     * @param bucket     桶
     * @param objectName 对象名称
     * @description 将文件写入minIO
     */
    @Override
    public void addMediaFilesToMinIO(String filePath, String bucket, String objectName) {
        UploadObjectArgs uploadObjectArgs = null;
        try {
            uploadObjectArgs = UploadObjectArgs.builder()
                    .bucket(bucket)
                    .object(objectName)
                    .filename(filePath)
                    .build();
            minioClient.uploadObject(uploadObjectArgs);
            log.debug("文件上传成功：{}", filePath);
        } catch (Exception e) {
            XueChengPlusException.cast("文件上传失败");
        }
    }

    /**
     * @param bytes      文件字节数组
     * @param bucket     桶
     * @param objectName 对象名称
     * @description 将文件写入minIO
     */
    private void addMediaFilesToMinIO(byte[] bytes, String bucket, String objectName) {
        //默认未知二进制流
        String contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
        try {
            //转为流
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
            PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                    .bucket(bucket)
                    .object(objectName)
                    //-1表示文件分片按5M(不小于5M,不大于5T),分片数量最大10000
                    .stream(byteArrayInputStream, byteArrayInputStream.available(), -1)
                    .contentType(contentType)
                    .build();

            minioClient.putObject(putObjectArgs);
        } catch (Exception e) {
            e.printStackTrace();
            XueChengPlusException.cast("文件上传出错");
        }
    }

    private String getMediaTypeByExtension(String extension) {
        //默认未知二进制流
        String contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
        if (StringUtils.isNotEmpty(extension)) {
            ContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(extension);
            if (extensionMatch != null) {
                contentType = extensionMatch.getMimeType();
            }
        }
        return contentType;
    }

    private String getFileFolder(Date date, boolean year, boolean month, boolean day) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        String format = sdf.format(date);
        String[] split = format.split("-");
        StringBuffer stringBuffer = new StringBuffer();
        if (year) {
            stringBuffer.append(split[0]);
            stringBuffer.append("/");
        }
        if (month) {
            stringBuffer.append(split[1]);
            stringBuffer.append("/");
        }
        if (day) {
            stringBuffer.append(split[2]);
            stringBuffer.append("/");
        }

        return stringBuffer.toString();
    }

    private String getChunkFileFolderPath(String fileMd5) {
        return fileMd5.substring(0, 1) + "/" + fileMd5.substring(1, 2) + "/" + fileMd5 + "/chunk/";
    }

    private String getObjectName(String fileMd5, String extension) {
        return fileMd5.substring(0, 1) + "/" + fileMd5.substring(1, 2) + "/" + fileMd5 + "/" + fileMd5 + extension;
    }
}
